package prolog.builtins

import prolog._
import prolog.fluents._
import prolog.io._
import prolog.terms._
import java.io.Writer
import java.io.Reader

//START PROTOTYPES TO ALLOW COMPILE

class CharReader(rdr:Reader,str:String,p:Prog) extends Source{
  
  var reader:Reader = null
  
  override def stop():Term = {return null}
  override def getElement():Term = {return null}
  
  def this(rdr:Reader, p:Prog) = this(rdr,null,p)
  
  def this(str:String, p:Prog) = this(null,str,p)
}
class CharWriter(wtr:Writer,str:String,p:Prog) extends Sink(p){
  
  var writer:Writer = null
  
  override def putElement(t:Term):Int = {return 0} 
  
  def this(wtr:Writer, p:Prog) = this(wtr,null,p)
  
  def this(str:String, p:Prog) = this(null,str,p)
}
class ClauseReader(rdr:Reader,str:String,p:Prog) extends CharReader(rdr,str,p){
  
  def this(rdr:Reader, p:Prog) = this(rdr,null,p)
  
  def this(str:String, p:Prog) = this(null,str,p)
  
  def this(p:Prog) = this(null,null,p)
}
class ClauseWriter(wtr:Writer,str:String,p:Prog) extends CharWriter(wtr,str,p){
  
  def this(wtr:Writer, p:Prog) = this(wtr,null,p)
  
  def this(str:String, p:Prog) = this(null,str,p)
  
  def this(p:Prog) = this(null,null,p)
}
//END PROTOTYPES

class Builtins extends HashDict {
	

def put(key:String,proto:Const){};
	
	
def register(proto: Const) : Unit = {
  var key:String = proto.name+"/"+0//proto.getArity; //TODO: figure out arity
  put(key,proto)
}

def newBuiltin(S:Const):Const = {
  var className:String = S.name
  var arity:Int = 0 //S.getArity //TODO: figure out arity
  var key:String = className+"/"+arity
  var b:Const = get().asInstanceOf[Const]
  return b
}

def toConstBuiltin(c:Const) : Const = {
  if(c.name.equals(Const.nil.name))
	  return Const.nil;
  if(c.name.equals(Const.no.name))
	  return Const.no;
  if(c.name.equals(Const.yes.name))
	  return Const.yes;

  var B:ConstBuiltin = Init.builtinDict.newBuiltin(c).asInstanceOf[ConstBuiltin]
  if(null==B) {
	  // IO.mes("not a builtin:"+this);
	  return c;
  }
  return B.asInstanceOf[Const];
}

  def toFunBuiltin(f:Fun) : Fun = {
    if(f.name.equals(":-")){//&&f.getArity==2) { //TODO: figure out arity
      return new Clause(f.args(0).asInstanceOf[Term],f.args(1).asInstanceOf[Term]).asInstanceOf[Fun]
    }
    if(f.name.equals(",")){//&&f.getArity==2) { //TODO: figure out arity
      return new Conj(f.args(0).asInstanceOf[Term],f.args(1).asInstanceOf[Term]).asInstanceOf[Fun]
    }
    var B:FunBuiltin = Init.builtinDict.newBuiltin(f.asInstanceOf[Const]).asInstanceOf[FunBuiltin];
    if(null==B)
      return f;
    B= B.funClone().asInstanceOf[FunBuiltin];
    B.args=f.args;
    return B;
  } 
} //end Builtins

class system extends FunBuiltin("system",1) {
  override def exec(p : Prog) : Int = {
    var cmd : String = getArg(0).asInstanceOf[Const].name
        return IO.system(cmd)
  }
}

class file_char_reader extends FunBuiltin("file_char_reader",2) {
  override def exec(p : Prog) : Int = {
    var I : Term = getArg(0)
    var f : Fluent = null
    if (I.isInstanceOf[CharReader])
      f = (new CharReader((I.asInstanceOf[CharReader]).reader,p)).asInstanceOf[Fluent]
    else {
      var s:String = (getArg(0).asInstanceOf[Const]).name
      f = (new CharReader(s,p)).asInstanceOf[Fluent]
    }
    return putArg(1,f,p)
  }
}

class file_clause_reader extends FunBuiltin("file_clause_reader",2) {
  override def exec(p:Prog):Int ={
    var I:Term=getArg(0);
    var f:Fluent = null
    if(I.isInstanceOf[CharReader])
      f=(new ClauseReader((I.asInstanceOf[CharReader]).reader,p)).asInstanceOf[Fluent]
    else {
      var s:String=(getArg(0).asInstanceOf[Const]).name
      f=(new ClauseReader(s,p)).asInstanceOf[Fluent]
    }
    return putArg(1,f,p);
  }
}

class char_file_writer extends FunBuiltin("char_file_writer",2) {
  override def exec(p : Prog):Int = {
    var s:String = (getArg(0).asInstanceOf[Const]).name
    var f:Fluent = (new CharWriter(s,p)).asInstanceOf[Fluent]
    return putArg(1,f,p)
  }
}

class clause_file_writer extends FunBuiltin("clause_file_writer",2) {
  override def exec(p : Prog):Int = {
    var s:String = (getArg(0).asInstanceOf[Const]).name
    var f:Fluent = (new ClauseWriter(s,p)).asInstanceOf[Fluent]
    return putArg(1,f,p)
  }
}

class get_stdin extends FunBuiltin("get_stdin",1) {
  override def exec(p : Prog):Int = {
    return putArg(0,(new ClauseReader(p)).asInstanceOf[Term],p)
  }
}

class get_stdout extends FunBuiltin("get_stdout",1) {
  override def exec(p : Prog):Int = {
    return putArg(0,(new ClauseWriter(p)).asInstanceOf[Term],p)
  }
}

class get_arity extends FunBuiltin("get_arity",2) {
  override def exec(p : Prog):Int ={
    var N:Int = 0 //getArg(0).getArity //TODO: figure out arity
    return putArg(1,N.asInstanceOf[Term],p);
  }
}

class stack_dump extends FunBuiltin("stack_dump",1) {
  override def exec(p : Prog):Int ={
    var s : String = getArg(0).toString();
    IO.error("User requested dump:" + (new Exception(s)));
    return 1;
  }
}

class set_max_answers extends FunBuiltin("set_max_answers",1){
  override def exec(p:Prog):Int ={
    IO.maxAnswers = getIntArg(0)
    return 1
  }
}

class reconsult_again extends ConstBuiltin("reconsult_again"){
  override def exec(p:Prog):Int ={
    return if(p.db.fromFile()) 1 else 0
    
  }
}

class get_default_db extends FunBuiltin("get_default_db",1){
  override def exec(p:Prog):Int = {
    return putArg(0,new ScalaObject(Init.default_db),p)
  }
}

class new_db extends FunBuiltin("new_db",1){
  override def exec(p:Prog):Int ={
    return putArg(0,new ScalaObject(new DataBase()),p)
  }
}

class db_add extends FunBuiltin("db_add",2){
  override def exec(p:Prog):Int={
    var db:DataBase = ((getArg(0).asInstanceOf[ScalaObject]).toObject()).asInstanceOf[DataBase]
    var X:Term = getArg(1)
    var key:String = X.getKey()
    if(key == null)
      return 0
    db.out(key,X)
    return 1    
  }
}

class db_remove extends FunBuiltin("db_remove",3){
  override def exec(p:Prog):Int={
    var db:DataBase=((getArg(0).asInstanceOf[ScalaObject]).toObject()).asInstanceOf[DataBase]
    var X:Term = getArg(1)
    var R:Term = db.cin(X.getKey(),X)
    return putArg(2,R,p)
  }
}

class db_collect extends FunBuiltin("db_collect",3){
  override def exec(p:Prog):Int={
    var db:DataBase=((getArg(0).asInstanceOf[ScalaObject]).toObject()).asInstanceOf[DataBase]
    var X:Term = getArg(1)
    var R:Term = db.cin(X.getKey(),X)
    return putArg(2,R,p)
  }
}

class db_source extends FunBuiltin("db_source",2){
  override def exec(p:Prog):Int={
    var db:DataBase=((getArg(0).asInstanceOf[ScalaObject]).toObject()).asInstanceOf[DataBase]
    var S:Source = new ScalaSource(db.toEnumeration(),p)
    return putArg(1,S,p)
  }
}

class at_key extends FunBuiltin("at_key",2){
  override def exec(p:Prog):Int={
    var R:Term = Init.default_db.all(getArg(0).getKey(),new Var())
    return putArg(1,R,p)
  }
}

class pred_to_string extends FunBuiltin("pred_to_string",2){
  override def exec(p:Prog):Int={
    var key:String=getArg(0).getKey()
    var listing:String=Init.default_db.pred_to_string(key)
    if(null==listing)
      return 0
    var R:Const=new Const(listing)
    return putArg(1,R,p)
  }
}

class db_to_string extends FunBuiltin("db_to_string",1){
  override def exec(p:Prog):Int={
    return putArg(0,new Const(Init.default_db.pprint()),p)
  }
}

class arg extends FunBuiltin("arg",3){
  override def exec(p:Prog):Int={
    var i:Int = getIntArg(0)
    var F:Fun = getArg(1).asInstanceOf[Fun]
    var A:Term = if(i == 0) new Const(F.name) else (if(i == -1) new Int_(F.getArity()) else F.args(i-1))
    return putArg(2,A,p)
  }
}